package com.filldream.fastboot.core.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.filldream.fastboot.base.entity.SysAppResources;
import com.filldream.fastboot.base.entity.SysAppResourcesHis;
import com.filldream.fastboot.base.entity.SysServerInfo;
import com.filldream.fastboot.base.mapper.SysAppResourceHisMapper;
import com.filldream.fastboot.base.mapper.SysAppResourceMapper;
import com.filldream.fastboot.base.mapper.SysServerInfoMapper;
import com.filldream.fastboot.base.request.ModifyStatusSTO;
import com.filldream.fastboot.base.request.pc.serverInfo.*;
import com.filldream.fastboot.base.response.pc.appResource.AppResourcePageDTO;
import com.filldream.fastboot.common.constant.FinalStr;
import com.filldream.fastboot.common.entity.ErrorCode;
import com.filldream.fastboot.common.exception.GlobalException;
import com.filldream.fastboot.common.util.Assert;
import com.filldream.fastboot.common.util.FileIOUtil;
import com.filldream.fastboot.common.util.ShellUtil;
import com.filldream.fastboot.common.util.TransferUtil;
import com.filldream.fastboot.core.service.SysAppResourceService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.util.*;

@AllArgsConstructor
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class SysAppResourceServiceImpl extends ServiceImpl<SysAppResourceMapper, SysAppResources> implements SysAppResourceService {

    private final SysServerInfoMapper serverInfoMapper;

    private final SysAppResourceHisMapper appHisMapper;

    private final Integer count = 30;

    @Override
    public List<String> ftp(String fileName, Long id) {
        Assert.isEmpty(id,"部署ID不能为空");

        //校验服务器和app配置
        SysAppResources app = baseMapper.selectById(id);
        Assert.isEmpty(app,"找不到APP配置");
        Assert.isBoolean( app.getStatus().equals(0),ErrorCode.BASE_ERR.reMsg("APP已禁用") );
        Assert.isEmpty( app.getBasePath() ,ErrorCode.BASE_ERR.reMsg("APP基本路径为空") );
        Assert.isEmpty( app.getAfterFileType() ,ErrorCode.BASE_ERR.reMsg("APP操作为空") );
        SysServerInfo serverInfo = serverInfoMapper.selectById(app.getServerId());
        Assert.isEmpty(serverInfo,"找不到服务器配置");
        Assert.isBoolean( serverInfo.getStatus().equals(0),ErrorCode.BASE_ERR.reMsg("服务器已禁用") );

        Integer afterFileType = app.getAfterFileType();
        String basePath = app.getBasePath();
        String shellTxt = app.getShellTxt();
        Integer basePort = app.getBasePort();

        List<String> resultList = new ArrayList<>();
        List<String> executeList = new ArrayList<>();
        List<String> errorList = new ArrayList<>();
        String jb = "";

        //配置ShellUtil
        ShellUtil shellUtil = new ShellUtil( serverInfo.getServerHost(), serverInfo.getServerUsername(), serverInfo.getServerPassword(), serverInfo.getServerPort() );

        //1、先判断是否存在这个目录
        jb = "cd " + basePath;
        executeList = toCmd(jb,"目录是否存在",shellUtil);
        for (String s : executeList) {
            if( s.indexOf("No such file or directory") >= 0 ){
                //创建文件夹
                shellUtil.executeForResultList("mkdir -p " + basePath);
            }
        }
        resultList.addAll( executeList );

        //5、操作不同，处理逻辑不同
        //部署解压缩
        if( afterFileType.equals(1) ){
            //fileName文件名，例如：aa-1234567890.rar
            Assert.isEmpty(fileName,"文件名不能为空");
            //原文件名，例如：aa.rar
            String serverFileName = fileName.substring(0,fileName.lastIndexOf("-")) + FileIOUtil.getFileSuffix(fileName);
            String fileType = fileName.substring(fileName.lastIndexOf(".")).substring(1).toLowerCase();
            //本地文件大小
            String fileSize = new File(FinalStr.getLocalFileUrl() + File.separator + fileName).length()+"";

            SysServerInfo thisServer = serverInfoMapper.selectById(1L);
            Assert.isEmpty(thisServer,"请先确保当前服务器已配置ServerHost");
            String fileUrl = "http://"  + (thisServer.getId().equals(serverInfo.getId()) ? "localhost" : thisServer.getServerHost()) +  ":" + FinalStr.getServerPort() + "/common/file/ftppwd/" + fileName;

            //1、判断环境是否支持后续操作
            hasCmd("wget",shellUtil);
            Map<String, String> unZipParam = getUnZipCmd(serverFileName);
            if( afterFileType.equals(1) ){
                hasCmd( unZipParam.get("hasCmd"),shellUtil );
            }
            if( fileType.equals("java") ){
                hasCmd("java",shellUtil);
            }

            //3、删除原目录
            jb = "rm -rf " + basePath + "/*";
            executeList = toCmd(jb,"删除目录",shellUtil);
            resultList.addAll( executeList );

            //4、更新文件并重命名
            jb = "cd " + basePath + " && wget -c " + fileUrl + " -O " + serverFileName;
            executeList = toCmd(jb,"更新文件并重命名",shellUtil);
            resultList.addAll( executeList );
            checkResult(executeList, Arrays.asList("‘" + serverFileName + "’ saved"),"服务器：文件下载失败",errorList);
            //查看文件大小并与之匹配
            jb = "cd " + basePath + " && du -b " + serverFileName;
            executeList = toCmd(jb, "新文件大小", shellUtil);
            checkResult(executeList, Arrays.asList(fileSize),"服务器：文件未100%下载",errorList);


            jb =  "cd " + basePath + " && " + unZipParam.get("cmd");
            executeList = toCmd(jb,"解压缩",shellUtil);
            resultList.addAll( executeList );
            checkResult(  executeList,Arrays.asList( unZipParam.get("ok")),"解压缩失败" ,errorList  );
        }else if( afterFileType.equals(2) ){
            //如果是端口则先暂停APP
            if( basePort > -1 ){
                executeList = toCmd(getStopAppCmd(basePort),"停止APP",shellUtil);
                resultList.addAll( executeList );
            }

            jb = "cd " + basePath + " && "+ shellTxt;
            executeList = toCmd(jb,"执行脚本",shellUtil);
            resultList.addAll( executeList );
            sleep(3); //等待三秒，等待脚本执行

            if( basePort > -1 ){
                log.info("应用启动中，请耐心等待启动结果，或者稍后手动查看运行状态");
                int i  = 0;
                boolean result = false;
                // 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败
                while (i++ < count){
                    result = checkPortStatus(basePort, shellUtil);
                    if(result){
                        break;
                    }
                    // 休眠6秒
                    sleep(6);
                }

                if( !result ){
                    errorList.add( "端口" + basePort + " 未检测到，故脚本执行失败" );
                }else{
                    executeList.add("---------do it ok---------");
                }
            }
            checkResult(executeList, Arrays.asList("do it ok"), "总结：脚本执行失败", errorList);
        }

        resultList.addAll( errorList );

        return resultList;
    }


    @Override
    public Page<AppResourcePageDTO> queryPage(AppResourcePageSTO appResourcePageSTO) {
        return baseMapper.queryPage(appResourcePageSTO.page(),appResourcePageSTO);
    }

    @Override
    public Boolean add(AddAppResourceSTO addSTO) {
        return save(TransferUtil.changeToObj(addSTO.check(), SysAppResources.class));
    }

    @Override
    public Boolean modify(ModifyAppResourceSTO modifyAppResourceSTO) {
        return updateById(TransferUtil.changeToObj(modifyAppResourceSTO.check(), SysAppResources.class));
    }

    @Override
    public Boolean del(Long id) {
        SysAppResources app = getById(id);
        Assert.isEmpty(app,"APP不存在");
        Assert.isBoolean( !app.getRunStatus().equals(0) && app.getBasePort() > -1,ErrorCode.BASE_ERR.reMsg("仅暂停的APP才可删除") );
        app.setDelFlag(1);
        return updateById( app );
    }

    @Override
    public Boolean modifyStatus(ModifyStatusSTO modifyStatusSTO) {

        SysAppResources app = getById(modifyStatusSTO.getId());
        Assert.isEmpty(app,"APP不存在");
        Assert.isBoolean( !app.getRunStatus().equals(0) && modifyStatusSTO.getStatus().equals(0) &&  app.getBasePort() > -1,ErrorCode.BASE_ERR.reMsg("仅暂停的APP才可禁用") );

        app.setStatus(modifyStatusSTO.getStatus());

        return updateById( app );
    }

    @Override
    public Boolean ftpCallBack(FtpCallBackAppResourceSTO ftpCallBackAppResourceSTO) {
        SysAppResources app = getById(ftpCallBackAppResourceSTO.getAppId());
        app.setRunStatus( ftpCallBackAppResourceSTO.getRunStatus() );
        app.setUpdateTime(new Date());
        app.setFileName(ftpCallBackAppResourceSTO.getFileName());

        boolean isBack = StringUtils.isNotBlank(ftpCallBackAppResourceSTO.getBackAppVersion());
        if(  ftpCallBackAppResourceSTO.getRunStatus().equals(1)  ){
            app.setAppVersion(  isBack ? ftpCallBackAppResourceSTO.getBackAppVersion() : nextVersion(app.getAppVersion())  );
        }
        boolean b = updateById(app);

        if( b ){
            //备份
            SysAppResourcesHis his = TransferUtil.changeToObj(app, SysAppResourcesHis.class);
            his.setUpdateTime(isBack ? new Date() : null);
            his.setCreateTime(null);
            his.setAppId( app.getId() );
            his.setId(null);
            return appHisMapper.insert( his ) > 0;
        }

        return false;
    }

    @Override
    public Boolean startOrStop(StartOrStopAppSTO startOrStopAppSTO) {
        LambdaQueryWrapper<SysAppResources> sql = new LambdaQueryWrapper<>();
        sql.eq(SysAppResources::getId,startOrStopAppSTO.getId() ).gt(SysAppResources::getBasePort,-1);
        SysAppResources app = getOne(sql);
        Assert.isEmpty(app,"APP不存在");
        Assert.isBoolean( app.getStatus().equals(0),ErrorCode.BASE_ERR.reMsg("APP已禁用") );
        Assert.isEmpty( app.getBasePath() ,ErrorCode.BASE_ERR.reMsg("APP基本路径为空") );
        Assert.isEmpty( app.getAfterFileType() ,ErrorCode.BASE_ERR.reMsg("APP操作为空") );
        SysServerInfo serverInfo = serverInfoMapper.selectById(app.getServerId());
        Assert.isEmpty(serverInfo,"服务器不存在");
        Assert.isEmpty(serverInfo,"找不到服务器配置");
        Assert.isBoolean( serverInfo.getStatus().equals(0),ErrorCode.BASE_ERR.reMsg("服务器已禁用") );

        ShellUtil executeShellUtil = new ShellUtil(serverInfo.getServerHost(),serverInfo.getServerUsername(),serverInfo.getServerPassword(),serverInfo.getServerPort());
        toCmd( getStopAppCmd(app.getBasePort()),"停止APP.",executeShellUtil );

        boolean result = false;
        if( startOrStopAppSTO.getIsStart() ){
            toCmd( "cd " + app.getBasePath() + " && " + app.getShellTxt(),"启动脚本",executeShellUtil );
            sleep(3);
            int i  = 0;
            // 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败
            while (i++ < count){
                result = checkPortStatus(app.getBasePort(), executeShellUtil);
                if(result){
                    break;
                }
                // 休眠6秒
                sleep(5);
            }
            executeShellUtil.close();
            return result;
        }else{
            result = checkPortStatus(app.getBasePort(), executeShellUtil);
            executeShellUtil.close();
            return !result;
        }
    }

    @Override
    public List<SysAppResourcesHis> ftpHis(AppHisPageSTO pageSTO) {
        LambdaQueryWrapper<SysAppResourcesHis> sql = new LambdaQueryWrapper<>();
        sql.eq(SysAppResourcesHis::getAppId,pageSTO.getAppId()).eq(SysAppResourcesHis::getDelFlag,0)
                .orderByDesc(SysAppResourcesHis::getCreateTime).last(" limit 15");
        return appHisMapper.selectList(sql);
    }

    @Override
    public List<String> ftpBack(Long id) {

        SysAppResourcesHis his = appHisMapper.selectById(id);
        Assert.isEmpty( his,"需要回退的版本不存在" );
        Assert.isBoolean( !his.getRunStatus().equals(1), ErrorCode.BASE_ERR.reMsg("需要回退的版本必须成功运行过"));

        List<String> ftp = ftp(his.getFileName(), his.getAppId());

        return ftp;
    }



    //获取不同文件的解压缩类型
    public Map<String,String> getUnZipCmd(String fileName){
        Map<String,String> resultMap = new HashMap<>();
        String fileType = fileName.substring(fileName.lastIndexOf(".")).substring(1).toLowerCase();
        if( fileType.equals("rar") ){
            resultMap.put("cmd"," rar x " + fileName);
            resultMap.put("ok","All OK");
        }else if(fileType.equals("zip")){
            resultMap.put("cmd"," unzip -o "+ fileName);
            resultMap.put("ok","Archive:  " + fileName);
        }else{
            throw  new GlobalException(ErrorCode.RES_TYPE_DENY.reMsg(fileType));
        }
        resultMap.put("hasCmd",fileType);
        return resultMap;
    }

    public String nextVersion(String oldV) {
        if(StringUtils.isBlank(oldV)){
            return "0.0.0.1";
        }
        String[] split = oldV.split("\\.");
        Integer v1 = Integer.valueOf(split[0]);
        Integer v2 = Integer.valueOf(split[1]);
        Integer v3 = Integer.valueOf(split[2]);
        Integer v4 = Integer.valueOf(split[3]);
        v4+=1;
        if(v4>=10){
            v3+=1;
            v4=0;
            if( v3 >=10 ){
                v2+=1;
                v3 = 0;
                if( v2 >=10 ){
                    v1+=1;
                    v2 = 0;
                }
            }
        }
        StringBuilder sb = new StringBuilder().append(v1).append("." + v2).append("." + v3).append("." + v4);
        return sb.toString();
    }

    public void checkResult(List<String> executeList,List<String> params,String msg,List<String> errorList){
        String resultMsg = "";
        for (String item : executeList) {
            resultMsg += item;
        }

        int size = 0;
        for (String item : params) {
            size = resultMsg.contains(item) ? size +1 : size;
        }

        if(size != params.size()){
            errorList.add(msg);
        }
    }

    public List<String> toCmd(String jb,String logName,ShellUtil shellUtil){
        List executeList = shellUtil.executeForResultList(jb);
        log.info( "{} 命令:{}",logName,jb );
        return executeList;
    }

    private boolean checkPortStatus(int port, ShellUtil shellUtil) {
        String result = shellUtil.executeForResult(String.format("fuser -n tcp %d", port));
        return result.indexOf("/tcp:")>0;
    }

    private void sleep(int second) {
        try {
            Thread.sleep(second * 1000);
        } catch (InterruptedException e) {
            log.error(e.getMessage(),e);
        }
    }

    //停止APP
    private String getStopAppCmd(int port) {
        return String.format("lsof -i :%d|grep -v \"PID\"|awk '{print \"kill -9\",$2}'|sh", port);
    }

    public void hasCmd(String cmd,ShellUtil shellUtil){
        List<String> resultList = toCmd("echo $(whereis " + cmd + ")", "判断是否有 " + cmd, shellUtil);
        boolean has = false;
        for (String item : resultList) {
            if( has == false  ){
                has = item.contains(cmd+": /");
            }else{
                return;
            }
        }
        shellUtil.close();
        Assert.isBoolean( has == false,ErrorCode.SERVER_CMD_ERR.reMsg(cmd + " 命令") );
    }

    public static void main(String[] args) {
        String a = "E:\\localFile\\fastboot-3636617277.jar";
        System.out.println(new File(a).length());
    }

}
